package com.jeebey.mybatis.interceptor;

import java.util.List;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.CachingExecutor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.SimpleExecutor;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import com.google.common.collect.Lists;
import com.jeebey.mybatis.dialect.Dialect;
import com.jeebey.mybatis.dialect.DialectFactory;
import com.jeebey.mybatis.query.Parameter;
import com.jeebey.mybatis.utils.MappedStatmentUtil;
import com.jeebey.mybatis.utils.SqlGenerateUtil;

/**通用DAO插件
 * @since 1.0
 * @author <a href="mailto:84961426@qq.com">JuST4iT</a>
 * @version $Id: GenericInterceptor.java Feb 18, 2016 6:05:34 PM $
 */

@Intercepts({
		@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }),
		@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
public class GenericInterceptor implements Interceptor {
	private final static String SQL_REGEX_ENTIRY = "Generic.entity";
	private final static String SQL_REGEX_CUSTOM = "Generic.custom";
	protected static Log log = LogFactory.getLog(GenericInterceptor.class);

	public Object intercept(Invocation invocation) throws Throwable {
		Object targetObject = invocation.getTarget();
		MetaObject executorMetaObject = MetaObject.forObject(targetObject, new DefaultObjectFactory(),
				new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
		while (executorMetaObject.hasGetter("h") || executorMetaObject.hasGetter("target")) {
			Object _hTargetObj = executorMetaObject.hasGetter("h") ? executorMetaObject.getValue("h") : null;
			Object _tTargetObj = executorMetaObject.hasGetter("target") ? executorMetaObject.getValue("target") : null;
			targetObject = _hTargetObj != null ? _hTargetObj : _tTargetObj;
			executorMetaObject = MetaObject.forObject(targetObject, new DefaultObjectFactory(),
					new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
		}
		if (targetObject instanceof CachingExecutor|| targetObject instanceof SimpleExecutor) {
			final MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
			final Object parameterObject = invocation.getArgs()[1];
			final String sourceSql = mappedStatement.getBoundSql(parameterObject).getSql();
			if (sourceSql.startsWith(SQL_REGEX_ENTIRY)||sourceSql.startsWith(SQL_REGEX_CUSTOM)) {
				String excuseSQL = null;
				List<Parameter> parameters = null;
				Class<?> entityClass = MappedStatmentUtil.transResourceToEntityClass(mappedStatement.getResource());
				if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {
					if(sourceSql.trim().equals("Generic.entity.insert.single")){
						excuseSQL = SqlGenerateUtil.createSqlInsertSingle(entityClass);
						parameters = SqlGenerateUtil.createParamsInsertSingle(entityClass, parameterObject,true);
					} else if (sourceSql.trim().equals("Generic.entity.insert.notnul")) { 
						excuseSQL = SqlGenerateUtil.createSqlInsertNotNul(entityClass,parameterObject);
						parameters = SqlGenerateUtil.createParamsInsertNotNul(entityClass, parameterObject);
					} else if (sourceSql.trim().equals("Generic.entity.replace.single")) { 
						if(!DialectFactory.isAnyDialect(Lists.newArrayList(Dialect.mysql,Dialect.mariadb))){
							throw new RuntimeException("当前选用数据库[ "+DialectFactory.CURR_DIALECT+" ] 不支持 REPLACE 语法");
						}
						excuseSQL = SqlGenerateUtil.createSqlReplaceSingle(entityClass);
						parameters = SqlGenerateUtil.createParamsRepleceSingle(entityClass, parameterObject);
					} else if (sourceSql.trim().equals("Generic.entity.insert.multiple")) { 
						excuseSQL = SqlGenerateUtil.createSqlInsertMultiple(entityClass,parameterObject);
						parameters = SqlGenerateUtil.createParamsInsertMultiple(entityClass, parameterObject);
					} else if(sourceSql.trim().equals("Generic.custom.execute.insert.sql")){
						excuseSQL = SqlGenerateUtil.createSqlExcute(parameterObject);
						if(!excuseSQL.trim().toLowerCase().startsWith("insert")){throw new RuntimeException("SQL NOT INSERT sql:"+excuseSQL);}
						parameters = SqlGenerateUtil.createParamsExcute(parameterObject);
					} 
				} else if (mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) {
					if (sourceSql.trim().equals("Generic.entity.update.single")) { 
						excuseSQL = SqlGenerateUtil.createSqlUpdateSingle(entityClass);
						parameters = SqlGenerateUtil.createParamsUpdateSingle(entityClass, parameterObject);
					}else if (sourceSql.trim().equals("Generic.entity.update.notnul")) { 
						excuseSQL = SqlGenerateUtil.createSqlUpdateNotNul(entityClass,parameterObject);
						parameters = SqlGenerateUtil.createParamsUpdateNotNul(entityClass, parameterObject);
					}else if(sourceSql.trim().equals("Generic.entity.update.single.criterion")){
						excuseSQL = SqlGenerateUtil.createSqlUpdateCondition(entityClass, parameterObject);
						parameters = SqlGenerateUtil.createParamsUpdateCondition(entityClass,parameterObject);
					}else if(sourceSql.trim().equals("Generic.entity.update.notnul.criterion")){
						excuseSQL = SqlGenerateUtil.createSqlUpdateNotNulCondition(entityClass, parameterObject);
						parameters = SqlGenerateUtil.createParamsUpdateNotNulCondition(entityClass,parameterObject);
					}else if(sourceSql.trim().equals("Generic.custom.execute.update.sql")){
						excuseSQL = SqlGenerateUtil.createSqlExcute( parameterObject);
						if(!excuseSQL.trim().toLowerCase().startsWith("update")){throw new RuntimeException("SQL NOT UPDATE sql:"+excuseSQL);}
						parameters = SqlGenerateUtil.createParamsExcute(parameterObject);
					}
				} else if (mappedStatement.getSqlCommandType() == SqlCommandType.DELETE) {
					if (sourceSql.trim().equals("Generic.entity.delete.id")) {
						excuseSQL = SqlGenerateUtil.createSqlDeleteId(entityClass);
						parameters = SqlGenerateUtil.createParamsDeleteId(entityClass, parameterObject);
					} else if(sourceSql.trim().equals("Generic.entity.delete.ids")) {
						excuseSQL = SqlGenerateUtil.createSqlDeleteIds(entityClass,parameterObject);
						parameters = SqlGenerateUtil.createParamsDeleteIds(entityClass, parameterObject);
					} else if(sourceSql.trim().equals("Generic.entity.delete.condition")) {
						excuseSQL = SqlGenerateUtil.createSqlDeleteCondition(entityClass, parameterObject);
						parameters = SqlGenerateUtil.createParamsDeleteCondition(entityClass,parameterObject);
					} else if(sourceSql.trim().equals("Generic.custom.execute.delete.sql")){
						excuseSQL = SqlGenerateUtil.createSqlExcute( parameterObject);
						if(!excuseSQL.trim().toLowerCase().startsWith("delete")){throw new RuntimeException("SQL NOT DELETE sql:"+excuseSQL);}
						parameters = SqlGenerateUtil.createParamsExcute(parameterObject);
					}
					
				} else if (mappedStatement.getSqlCommandType() == SqlCommandType.SELECT) {
					if (sourceSql.trim().equals("Generic.entity.select.id")) { 
						excuseSQL = SqlGenerateUtil.createSqlSelectId(entityClass);
						parameters = SqlGenerateUtil.createParamsSelectId(entityClass, parameterObject);
					} else if (sourceSql.trim().equals("Generic.entity.select.all")) { 
						excuseSQL = SqlGenerateUtil.createSqlSelectAll(entityClass);
						parameters = SqlGenerateUtil.createParamsSelectAll();
					} else if (sourceSql.trim().equals("Generic.entity.select.all.count")) { 
						excuseSQL = SqlGenerateUtil.createSqlSelectAllCount(entityClass);
						parameters = SqlGenerateUtil.createParamsSelectAllCount();
					}  else if (sourceSql.trim().equals("Generic.entity.select.ids")){
						excuseSQL = SqlGenerateUtil.createSqlSelectIds(entityClass,parameterObject);
						parameters = SqlGenerateUtil.createParamsSelectIds(entityClass, parameterObject);
					} else if (sourceSql.trim().equals("Generic.entity.select.condition.unique")) {
						excuseSQL = SqlGenerateUtil.createSqlSelectCondition(entityClass, parameterObject);
						parameters = SqlGenerateUtil.createParamsSelectCondition(entityClass,parameterObject);
					} else if (sourceSql.trim().equals("Generic.entity.select.condition.multiple")) {
						excuseSQL = SqlGenerateUtil.createSqlSelectCondition(entityClass, parameterObject);
						parameters = SqlGenerateUtil.createParamsSelectCondition(entityClass,parameterObject);
					} else if (sourceSql.trim().equals("Generic.entity.select.condition.count")) {
						excuseSQL = SqlGenerateUtil.createSqlSelectConditionCount(entityClass, parameterObject);
						parameters = SqlGenerateUtil.createParamsSelectConditionCount(entityClass,parameterObject);
					} else if (sourceSql.trim().equals("Generic.entity.select.condition.rowbounds")) {
						excuseSQL = SqlGenerateUtil.createSqlSelectCondition(entityClass, parameterObject);
						parameters = SqlGenerateUtil.createParamsSelectConditionRowbounds(entityClass,parameterObject);
					} else if (sourceSql.trim().equals("Generic.entity.select.condition.bounds")) {
						excuseSQL = SqlGenerateUtil.createSqlSelectConditionBounds(entityClass, parameterObject);
						parameters = SqlGenerateUtil.createParamsSelectConditionBounds(entityClass,parameterObject);
					} else if (sourceSql.trim().equals("Generic.entity.select.condition.paging")) {
						excuseSQL = SqlGenerateUtil.createSqlSelectConditionPaging(entityClass, parameterObject);
						parameters = SqlGenerateUtil.createParamsSelectConditionPaging(entityClass,parameterObject);
					} else if (sourceSql.trim().equals("Generic.entity.clear.cache")) {
						excuseSQL = DialectFactory.CURR_DIALECT_IMPL.createClearCacheSql();
						parameters = Lists.newArrayList();
					} else if (sourceSql.trim().equals("Generic.custom.execute.select.sql")) {
						Object[] objs= SqlGenerateUtil.createSqlExcuteClass(parameterObject);
						excuseSQL = (String)objs[1]; entityClass = (Class<?>)objs[0];
						if(!excuseSQL.trim().toLowerCase().startsWith("select")){throw new RuntimeException("SQL NOT SELECT sql:"+excuseSQL);}
						parameters = SqlGenerateUtil.createParamsExcute(parameterObject);
					} 
				}
				invocation.getArgs()[0] = MappedStatmentUtil.createGenericMappedStatement(entityClass,mappedStatement, excuseSQL, parameterObject, parameters,DialectFactory.CURR_DIALECT_IMPL);
			}
		}
		return invocation.proceed();
	}

	public Object plugin(Object object) {
		return Plugin.wrap(object, this);
	}

	public void setProperties(Properties properties) {
		String _dialectType = properties.getProperty("dialect");
		if (StringUtils.isEmpty(_dialectType)) {
			//DialectFactory.CURR_DIALECT = new MysqlDialect();
			return;
		}
		DialectFactory.createDialect(Dialect.of(_dialectType));
	}
}
